﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using ObjectTransposeApi.Handler.Expand;
using ObjectTransposeApi.Structure;
using System.Collections;
using ObjectTransposeApi.Handler.Property;
using ObjectTransposeApi.Extension;
using ObjectTransposeApi.Helper;

namespace ObjectTransposeApi
{
    /// <summary/>
    public static class ObjectToValue
    {
        /// <summary/>
        public static T Copy<T>(
            object input,
            params AbstractMapper[] mappers) where T : new()
        {
            PropertyMapper[] propertyMappers = Array.FindAll(mappers, value => typeof(PropertyMapper).IsInstanceOfType(value)).Select(value => (PropertyMapper)value).ToArray();
            IndexMapper[] indexMappers = Array.FindAll(mappers, value => typeof(IndexMapper).IsInstanceOfType(value)).Select(value => (IndexMapper)value).ToArray();
            TypeMapper[] typeMappers = Array.FindAll(mappers, value => typeof(TypeMapper).IsInstanceOfType(value)).Select(value => (TypeMapper)value).ToArray();

            AbstractExpandHandler expandHandler = ChainHelper.FetchExpandHandlers();

            T destination = new T();

            Queue<QueueStructure> queueStructures = new Queue<QueueStructure>();

            // the fist thing that needs to be is expand the input variable ... if it is a collection then
            // each item in the collection needs to tested for a match against the destination object
            foreach (object source in expandHandler.Process(input))
            {
                // add the items .. the getter property on the destination is needed to handle collections
                queueStructures.Enqueue(new QueueStructure(
                    destination,
                    ReflectionHelper.FetchGetSetProperties(destination.GetType()),
                    source,
                    ReflectionHelper.FetchGetProperties(source.GetType())));
            }

            AbstractPropertyHandler propertyHandler = ChainHelper.FetchPropertyHandler();

            // process each queued item
            while (queueStructures.Count > 0)
            {
                QueueStructure queueStructure = queueStructures.Dequeue();

                // enum the destination properties
                foreach (PropertyInfo destinationPropertyInfo in queueStructure.DestinationPropertyInfos)
                {
                    // find a matching source property
                    PropertyInfo sourcePropertyInfo = ReflectionHelper.FetchMatchingProperty(
                        queueStructure.SourcePropertyInfos,
                        destinationPropertyInfo.Name,
                        propertyMappers);

                    // if a matching source property is not found then move on
                    if (sourcePropertyInfo == null)
                    {
                        continue;
                    }

                    IndexMapper indexMapper = Array.Find(indexMappers, value => value.SourceName == sourcePropertyInfo.Name);

                    // build the structure needed by the handler
                    PropertyStructure propertyStructure = new PropertyStructure()
                    {
                        ExpandCallback = expandHandler.Process,
                        Destination = queueStructure.Destination,
                        DestinationPropertyInfo = destinationPropertyInfo,
                        Source = queueStructure.Source,
                        SourcePropertyInfo = sourcePropertyInfo,
                        IndexMapper = indexMapper,
                        TypeMappers = typeMappers,
                    }; 
                    
                    // process the structure and add new items if any
                    queueStructures.EnqueueRange(propertyHandler.Process(propertyStructure));
                }
            }

            // return the object
            return destination;
        }
    }
}
